package com.griddroid.serverdaemons.threadhandlers;

import java.io.IOException;
import java.net.Socket;
import com.griddroid.configuration.GridConfig;
import com.griddroid.messages.EndSessionRequestMessage;
import com.griddroid.messages.PingAckMessage;
import com.griddroid.nodes.GridServer;
import com.griddroid.serverdaemons.SplitterDaemon;
import com.griddroid.serverdaemons.WorkerDaemon;
import com.griddroid.works.WorkRequest;
import com.griddroid.works.WorkResponse;

/**
 * Is responsible for the communication between the server and a worker.
 * <p>
 * It is a part of the server, that keeps alive the communication between the
 * server and a worker. It is also responsible for sending the ping message
 * every a specified time interval, for checking if new messages have arrived
 * and for deleting a non-responsive worker
 * 
 * @author Thomas Ailianos
 * @author Charalampos Mavroforakis
 * @author Michail Michailidis
 * 
 */
public class WorkerHandler extends GenericHandler
{
	private WorkRequest currentWork;
	private boolean hasSentWork = false;

	/**
	 * Constructs the worker handler, given the socket and the server
	 * 
	 * @param workerSocket
	 *            the socket with which the worker tried to connect to the
	 *            server. The worker handler will establish a new one.
	 * @param server
	 *            the parent of the worker handler. This will make the use of
	 *            shared collections easier
	 * @throws IOException
	 */
	public WorkerHandler(Socket workerSocket, GridServer server) throws IOException
	{
		super();
		this.server = server;
		this.server.log("Info", "Creating new worker handler");
		this.setId(this.server.getWorkerDaemon().getAvailableId());
		this.firstSocket = workerSocket;
	}

	/**
	 * The actual functionality of the worker handler. It is a continuous loop
	 * that goes through the following steps:
	 * <ol>
	 * <li>gets the socket through which the worker tried to connect to the
	 * server. It will be used to send back the new port, on which the permanent
	 * connection will be establish</li>
	 * <li>checks if there are any pending work requests from the
	 * {@link SplitterDaemon Splitter} and sends them to the worker</li>
	 * <li>checks if the worker sent a ping acknowledgment message</li>
	 * <li>checks if the worker send an end-session request, it calls
	 * {@link #close() close()} and deletes itself from the worker daemon using
	 * {@link WorkerDaemon#deleteWorkerHandler(String)
	 * deleteWorkerHandler(String)}</li>
	 * <li>checks if the worker sent a work response. It adds it to the appropriate queue
	 * the {@link GridServer server}</li>
	 * <li>checks if the worker did not reply to a ping for a long time period,
	 * so it breaks the connection and deletes itself</li>
	 * </ol>
	 */
	public void run()
	{
		this.server.log("Info", "Worker Handler started running");
		this.acceptConnection();
		// first accept the new connection,then ping, then send
		// workfragmentrequest-response
		while (true)
		{
			if (this.forceExit)
				return;
			checkCounter++;
			// SEND PING to the worker
			this.sendPings();

			// Check for new messages and handle them
			try
			{
				while (this.permanentSocket.getInputStream().available() > 0)
				{

					Object temp = this.receive(this.permanentSocket);
					if (temp instanceof PingAckMessage)
					{
						this.acceptPingAckMessage(temp);
					}
					else if (temp instanceof EndSessionRequestMessage)
					{
						this.acceptEndSessionMessage(temp);
						this.close();
						this.server.getWorkerDaemon().deleteWorkerHandler(this.getId());
						return;
					}
					else if (temp instanceof WorkResponse)
					{
						WorkResponse wr = (WorkResponse) temp;
						this.server.log("Info", "Worker handler with id " + this.getId() + " just received a work response");
						this.server.getWorkFragmentResponses().add(wr);
						this.currentWork = null;
						this.hasSentWork = false;
					}
					else
					{
						this.server.log("Error", "Worker hanlder with id " + this.getId() + " received unknown message");
					}
				}
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
			catch (ClassNotFoundException e)
			{
				e.printStackTrace();
			}

			// Send available messages
			if (!this.hasSentWork && !this.isAvailable())
			{
				try
				{
					this.send(this.permanentSocket, this.currentWork);
					this.server.log("Info", "Work hanlder with id " + this.getId() + " just sent a work request to the worker.");
				}
				catch (IOException e)
				{
					this.server.log("Error", "Work handler with id : " + this.getId() + " could not send work to the worker");
				}
				this.hasSentWork = true;
			}

			// Check for timeouts
			if (!this.checkPingsTimeouts())
			{
				this.server.getWorkerDaemon().deleteWorkerHandler(this.getId());
			}

			try
			{
				Thread.sleep(GridConfig.CHECKINTERVAL);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}

	/**
	 * Before closing, the worker handler closes the socket and releases the
	 * port. Thus it cleans all of its traces on the server. Finally it writes
	 * that event to the log
	 */
	@Override
	public void close()
	{
		if (this.permanentSocket != null)
		{
			try
			{
				this.permanentSocket.shutdownInput();
				this.permanentSocket.shutdownOutput();
				this.permanentSocket.close();
				this.handlerSocket.close();
			}
			catch (IOException e)
			{
				System.out.println(e.getMessage());
			}
		}
		this.server.freePort(this.currentPort);
		this.server.log("Error", "Exiting hanlder with id " + this.getId());
		if (this.hasSentWork)
			this.server.getWorkFragmentRequests().add(this.currentWork);
		// this.server.getWorkerDaemon().deleteWorkerHandler(this.getId());
		this.forceExit = true;
	}

	public boolean isAvailable()
	{
		return (this.currentWork == null ? true : false);
	}

	/**
	 * Assigns a work to send to the worker
	 * 
	 * @param wr
	 *            a work request that will be forwarded to the worker
	 */
	public void getWork(WorkRequest wr)
	{
		this.currentWork = wr;
		this.hasSentWork = false;
	}
}
